// Copyright (C) ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
//
// $Id: day_counter.cpp 225 2008-09-12 17:43:02Z heavyzheng $
//

#ifndef ZZHENG_QUANT_DATES_SOURCE
#define ZZHENG_QUANT_DATES_SOURCE
#endif // ZZHENG_QUANT_DATES_SOURCE

#include <zzheng/quant/dates/day_counter.hpp>
#include <zzheng/quant/dates/exception.hpp>
#include <zzheng/quant/dates/basis.hpp>
#include <zzheng/quant/dates/date.hpp>
#include <zzheng/quant/dates/calendar.hpp>

#include <zzheng/quant/dates/day_count/actual_360.hpp>
#include <zzheng/quant/dates/day_count/actual_365.hpp>

#ifdef ZZHENG_MSVC
#  pragma warning(push)
#  pragma warning(disable: 4100 4512)
#endif // ZZHENG_MSVC

#include <boost/variant.hpp>

#ifdef ZZHENG_MSVC
#  pragma warning(pop)
#endif // ZZHENG_MSVC

#include <boost/noncopyable.hpp>
#include <cassert>
#include <string>
#include <map>

namespace zzheng {
namespace quant {
namespace dates {

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // anonymous namespace
    //

    namespace {

        //! Typedef of the day count convention as a variant.
        typedef boost::variant< detail::actual_360,
                                detail::actual_365
                              > convention;

        //! The day count convention factory singleton class.
        class convention_factory: private boost::noncopyable {

        public:

            //! Returns the singleton instance.
            //! \return the singleton instance.
            static convention_factory& instance() {
                static convention_factory instance;
                return instance;
            }

            //! Finds the day count convention object by basis. It is guaranteed that each basis
            //! value have a count convention object associated with it.
            //! \param basis  the basis to find the day count convention.
            //! \return the day count convention object found.
            convention find_convention(basis_t basis) const {
                typedef std::map<basis_t, convention>::const_iterator const_iterator;
                const_iterator i = conventions_.find(basis);
                assert(i != conventions_.end() && "Day count convention should always be found.");
                if (i != conventions_.end()) {
                    return i->second;
                } else {
                    std::string errmsg = "Fail to find day count convention for basis "
                                       + to_string(basis);
                    throw date_error(errmsg.c_str());
                }
            }

        private:

            //! Private constructor to register the day count convention objects.
            explicit convention_factory(): conventions_() {
                conventions_.insert( std::make_pair(actual_360, detail::actual_360()) );
                conventions_.insert( std::make_pair(actual_365, detail::actual_365()) );
            }

            //! Private destructor.
            ~convention_factory() {
                // Do nothing.
            }

        private:

            std::map<basis_t, convention> conventions_; //!< The day count convention map.

        }; // class convention_factory

        //! The visitor class to visit the day count convention variant.
        class convention_visitor: public boost::static_visitor<double> {

        public:

            explicit convention_visitor(const date& start, const date& end, const calendar& cal)
            : start_(start), end_(end), cal_(&cal) {
                // Do nothing.
            }

            // Use auto-generated copy constructor.
            // Use auto-generated copy assignment.
            // Use auto-generated destructor.

            template<typename T>
            double operator()(T& operand) const {
                return operand.year_fraction(start_, end_, *cal_);
            }

        private:

            date            start_;
            date            end_;
            const calendar* cal_;

        }; // class convention_visitor

    } // anonymous namespace


    ////////////////////////////////////////////////////////////////////////////////////////////////
    // day_counter
    //

    day_counter::day_counter(basis_t basis): basis_(basis) {
        // Do nothing.
    }

    basis_t day_counter::basis() const {
        return basis_;
    }

    double day_counter::year_fraction(const date& start,
                                      const date& end,
                                      const calendar& cal) const {
        const convention_factory& factory = convention_factory::instance();
        convention convention = factory.find_convention(basis_);
        return boost::apply_visitor( convention_visitor(start, end, cal), convention );
    }

} // namespace zzheng::quant::dates
} // namespace zzheng::quant
} // namespace zzheng



